//===----------------------------------------------------------------------===//
//
//                         BusTub
//
// decimal_type.cpp
//
// Identification: src/type/decimal_type.cpp
//
// Copyright (c) 2015-2019, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#include <cassert>
#include <cmath>
#include <iostream>
#include <string>

#include "common/exception.h"
#include "type/float_type.h"

namespace benxdb {
#define FLOAT_COMPARE_FUNC(OP)                                          \
  switch (right.GetTypeId()) {                                            \
    case TypeId::TINYINT:                                                 \
      return GetCmpBool(left.value_.float_ OP right.GetAs<int8_t>());   \
    case TypeId::SMALLINT:                                                \
      return GetCmpBool(left.value_.float_ OP right.GetAs<int16_t>());  \
    case TypeId::INTEGER:                                                 \
      return GetCmpBool(left.value_.float_ OP right.GetAs<int32_t>());  \
    case TypeId::BIGINT:                                                  \
      return GetCmpBool(left.value_.float_ OP right.GetAs<int64_t>());\
    case TypeId::FLOAT:                                                  \
      return GetCmpBool(left.value_.float_ OP right.GetAs<float>());      \
    case TypeId::DECIMAL:                                                 \
      return GetCmpBool(left.value_.float_ OP right.GetAs<double>());   \
    case TypeId::VARCHAR: {                                               \
      auto r_value = right.CastAs(TypeId::DECIMAL);                       \
      return GetCmpBool(left.value_.float_ OP r_value.GetAs<double>()); \
    }                                                                     \
    default:                                                              \
      break;                                                              \
  }  // SWITCH

#define FLOAT_MODIFY_FUNC(OP)                                                       \
  switch (right.GetTypeId()) {                                                        \
    case TypeId::TINYINT:                                                             \
      return Value(TypeId::FLOAT, left.value_.float_ OP right.GetAs<int8_t>());   \
    case TypeId::SMALLINT:                                                            \
      return Value(TypeId::FLOAT, left.value_.float_ OP right.GetAs<int16_t>());  \
    case TypeId::INTEGER:                                                             \
      return Value(TypeId::FLOAT, left.value_.float_ OP right.GetAs<int32_t>());  \
    case TypeId::BIGINT:                                                              \
      return Value(TypeId::FLOAT, left.value_.float_ OP right.GetAs<int64_t>());  \
    case TypeId::FLOAT:                                                             \
      return Value(TypeId::FLOAT, left.value_.float_ OP right.GetAs<float>());   \
    case TypeId::DECIMAL:                                                             \
      return Value(TypeId::FLOAT, left.value_.float_ OP right.GetAs<float>());   \
    case TypeId::VARCHAR: {                                                           \
      auto r_value = right.CastAs(TypeId::FLOAT);                                   \
      return Value(TypeId::FLOAT, left.value_.float_ OP r_value.GetAs<float>()); \
    }                                                                                 \
    default:                                                                          \
      break;                                                                          \
  }  // SWITCH

// static inline double ValMod(double x, double y) {
//  return x - std::trunc((double)x / (double)y) * y;
//}

FloatType::FloatType() : NumericType(TypeId::FLOAT) {}

bool FloatType::IsZero(const Value &val) const {
  assert(GetTypeId() == TypeId::FLOAT);
  return (val.value_.float_ == 0);
}

Value FloatType::Add(const Value &left, const Value &right) const {
  assert(GetTypeId() == TypeId::FLOAT);
  assert(left.CheckComparable(right));
  if (left.IsNull() || right.IsNull()) {
    return left.OperateNull(right);
  }

  FLOAT_MODIFY_FUNC(+);  // NOLINT
  throw Exception("type error");
}

Value FloatType::Subtract(const Value &left, const Value &right) const {
  assert(GetTypeId() == TypeId::FLOAT);
  assert(left.CheckComparable(right));
  if (left.IsNull() || right.IsNull()) {
    return left.OperateNull(right);
  }

    FLOAT_MODIFY_FUNC(-);  // NOLINT

  throw Exception("type error");
}

Value FloatType::Multiply(const Value &left, const Value &right) const {
  assert(GetTypeId() == TypeId::FLOAT);
  assert(left.CheckComparable(right));
  if (left.IsNull() || right.IsNull()) {
    return left.OperateNull(right);
  }

    FLOAT_MODIFY_FUNC(*);  // NOLINT
  throw Exception("type error");
}

Value FloatType::Divide(const Value &left, const Value &right) const {
  assert(GetTypeId() == TypeId::FLOAT);
  assert(left.CheckComparable(right));
  if (left.IsNull() || right.IsNull()) {
    return left.OperateNull(right);
  }

  if (right.IsZero()) {
    throw Exception(ExceptionType::DIVIDE_BY_ZERO, "Division by zero on right-hand side");
  }

    FLOAT_MODIFY_FUNC(/);  // NOLINT

  throw Exception("type error");
}

Value FloatType::Modulo(const Value &left, const Value &right) const {
  assert(GetTypeId() == TypeId::FLOAT);
  assert(left.CheckComparable(right));
  if (left.IsNull() || right.IsNull()) {
    return OperateNull(left, right);
  }

  if (right.IsZero()) {
    throw Exception(ExceptionType::DIVIDE_BY_ZERO, "Division by zero on right-hand side");
  }
  switch (right.GetTypeId()) {
    case TypeId::TINYINT:
      return Value(TypeId::FLOAT, ValMod(left.value_.float_, right.GetAs<int8_t>()));
    case TypeId::SMALLINT:
      return Value(TypeId::FLOAT, ValMod(left.value_.float_, right.GetAs<int16_t>()));
    case TypeId::INTEGER:
      return Value(TypeId::FLOAT, ValMod(left.value_.float_, right.GetAs<int32_t>()));
    case TypeId::BIGINT:
      return Value(TypeId::FLOAT, ValMod(left.value_.float_, right.GetAs<int64_t>()));
    case TypeId::DECIMAL:
      return Value(TypeId::FLOAT, ValMod(left.value_.float_, right.GetAs<float>()));
    case TypeId::VARCHAR: {
      auto r_value = right.CastAs(TypeId::FLOAT);
      return Value(TypeId::FLOAT, ValMod(left.value_.float_, r_value.GetAs<float>()));
    }
    default:
      break;
  }
  throw Exception("type error");
}

Value FloatType::Min(const Value &left, const Value &right) const {
  assert(GetTypeId() == TypeId::FLOAT);
  assert(left.CheckComparable(right));
  if (left.IsNull() || right.IsNull()) {
    return left.OperateNull(right);
  }

  if (left.CompareLessThanEquals(right) == CmpBool::CmpTrue) {
    return left.Copy();
  }
  return right.Copy();
}

Value FloatType::Max(const Value &left, const Value &right) const {
  assert(GetTypeId() == TypeId::FLOAT);
  assert(left.CheckComparable(right));
  if (left.IsNull() || right.IsNull()) {
    return left.OperateNull(right);
  }

  if (left.CompareGreaterThanEquals(right) == CmpBool::CmpTrue) {
    return left.Copy();
  }
  return right.Copy();
}

Value FloatType::Sqrt(const Value &val) const {
  assert(GetTypeId() == TypeId::FLOAT);
  if (val.IsNull()) {
    return Value(TypeId::FLOAT, BUSTUB_DECIMAL_NULL);
  }
  if (val.value_.decimal_ < 0) {
    throw Exception(ExceptionType::DECIMAL, "Cannot take square root of a negative number.");
  }
  return Value(TypeId::FLOAT, std::sqrt(val.value_.float_));
}


Value FloatType::OperateNull(const Value &left __attribute__((unused)),
                               const Value &right __attribute__((unused))) const {
    return Value(TypeId::DECIMAL, BUSTUB_DECIMAL_NULL);
}

CmpBool FloatType::CompareEquals(const Value &left, const Value &right) const {
    assert(GetTypeId() == TypeId::FLOAT);
    assert(left.CheckComparable(right));
    if (left.IsNull() || right.IsNull()) {
        return CmpBool::CmpNull;
    }

    FLOAT_COMPARE_FUNC(==);  // NOLINT

    throw Exception("type error");
}

CmpBool FloatType::CompareNotEquals(const Value &left, const Value &right) const {
    assert(GetTypeId() == TypeId::FLOAT);
    assert(left.CheckComparable(right));
    if (left.IsNull() || right.IsNull()) {
        return CmpBool::CmpNull;
    }

    FLOAT_COMPARE_FUNC(!=);  // NOLINT

    throw Exception("type error");
}

CmpBool FloatType::CompareLessThan(const Value &left, const Value &right) const {
    assert(GetTypeId() == TypeId::FLOAT);
    assert(left.CheckComparable(right));
    if (left.IsNull() || right.IsNull()) {
        return CmpBool::CmpNull;
    }

    FLOAT_COMPARE_FUNC(<);  // NOLINT

    throw Exception("type error");
}

CmpBool FloatType::CompareLessThanEquals(const Value &left, const Value &right) const {
    assert(GetTypeId() == TypeId::FLOAT);
    assert(left.CheckComparable(right));
    if (left.IsNull() || right.IsNull()) {
        return CmpBool::CmpNull;
    }

    FLOAT_COMPARE_FUNC(<=);  // NOLINT

    throw Exception("type error");
}

CmpBool FloatType::CompareGreaterThan(const Value &left, const Value &right) const {
    assert(GetTypeId() == TypeId::FLOAT);
    assert(left.CheckComparable(right));
    if (left.IsNull() || right.IsNull()) {
        return CmpBool::CmpNull;
    }

    FLOAT_COMPARE_FUNC(>);  // NOLINT

    throw Exception("type error");
}

CmpBool FloatType::CompareGreaterThanEquals(const Value &left, const Value &right) const {
    assert(GetTypeId() == TypeId::FLOAT);
    assert(left.CheckComparable(right));
    if (left.IsNull() || right.IsNull()) {
        return CmpBool::CmpNull;
    }

    FLOAT_COMPARE_FUNC(>=);  // NOLINT

    throw Exception("type error");
}

std::string FloatType::ToString(const Value &val) const {
    return Type::ToString(val);
}

void FloatType::SerializeTo(const Value &val, char *storage) const {
    Type::SerializeTo(val, storage);
}

Value FloatType::DeserializeFrom(const char *storage) const {
    return Type::DeserializeFrom(storage);
}

Value FloatType::Copy(const Value &val) const {
    return Type::Copy(val);
}

Value FloatType::CastAs(const Value &val, TypeId type_id) const {
    return Type::CastAs(val, type_id);
}

}  // namespace bustub
